package com.zlc.atonce.huarongdao.algorithme;

import com.zlc.atonce.Util.ArrayUtil;
import com.zlc.atonce.Util.Point;

import java.util.*;


public class HRDImpl {
    private static final Point[] arrayHelper = new Point[20];
    public final int rows, cols;
    final char[][] cells;
    final Point[] blanks;
    Point[][] owners;// 所属方块坐上角位置
    Map<Point, List<Point>> ownerRegions = new HashMap<>();
    String id;

    private HRDImpl(String id) {
        this.id = id;
        String[] split = id.split(",");
        int length = split.length;
        this.rows = length;
        this.cols = split[0].length();
        char[][] t = new char[length][];
        int blankSize = 0;
        for (int i = 0; i < length; i++) {
            char[] tt = split[i].toCharArray();
            t[i] = tt;
            for (int j = 0; j < tt.length; j++) {
                if (CellCoder.isBlank(tt[j])) {
                    arrayHelper[blankSize++] = Point.of(i, j);
                }
            }
        }
        this.blanks = new Point[blankSize];
        System.arraycopy(arrayHelper, 0, this.blanks, 0, blankSize);
        this.cells = t;
        initOwner(t);

    }

    /**
     * 输入编码格式：ABCD,ABCE,ABCD,ABCE,ABCD
     */
    public static HRDImpl fromIdentity(String id) {
        // todo cache this
        return new HRDImpl(id);
    }

    static String char2id(char[][] c) {
        StringBuilder sb = new StringBuilder();
        for (char[] cell : c) {
            sb.append(cell).append(',');
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

    public static boolean isTarget(String identity) {
        return identity.charAt(21) == Cell.FOUR_LD.charCode;
    }

    public Map<Point, List<Point>> getOwnerRegions() {
        return ownerRegions;
    }

    void initOwner(char[][] src) {
        Map<Point, List<Point>> map = new HashMap<>();
        Point[][] re = new Point[this.rows][];
        for (int i = 0; i < this.rows; i++) {
            re[i] = new Point[this.cols];
            for (int j = 0; j < this.cols; j++) {
                char charCode = src[i][j];
                if (CellCoder.isBlank(charCode)) {
                    continue;
                }
                Point now = Point.of(i, j);

                Point point = CellCoder.offset2LU(Cell.char2intMap.get(charCode));
                point = now.add(point);
                re[i][j] = point;
                if (map.containsKey(point)) {
                    map.get(point).add(now);
                } else {
                    List<Point> tt = new ArrayList<>();
                    tt.add(now);
                    map.put(point, tt);
                }
            }
        }
        this.owners = re;
        this.ownerRegions = map;
    }


    public Point[] getBlankPoint() {
        return this.blanks;
    }

    public int getCellByPos(Point pos) {
        if (hasPos(pos)) {
            return Cell.char2intMap.get(cells[pos.getX()][pos.getY()]);
        } else {
            return 0;
        }
    }

    public boolean hasPos(Point point) {
        return point.inBound(0, 0, this.rows - 1, this.cols - 1);
    }

    public HRDImpl move(Point src, Point delta) {
        Point owner = getOwnerPos(src);
        List<Point> points = ownerRegions.get(owner);
        char[][] copy = ArrayUtil.arrayCopy(this.cells);
        for (Point point : points) {
            copy[point.getX()][point.getY()] = Cell.BLANK.charCode;
        }
        for (Point point : points) {
            Point add = point.add(delta);
            copy[add.getX()][add.getY()] = this.cells[point.getX()][point.getY()];
        }
        return HRDImpl.fromIdentity(char2id(copy));
    }


    public boolean canMove(Point src, Point delta) {
        Point owner = getOwnerPos(src);
        List<Point> points = ownerRegions.get(owner);
        for (Point point : points) {
            Point newPos = point.add(delta);
            if (!hasPos(newPos)) {
                return false;
            }
            char toMove = this.cells[newPos.getX()][newPos.getY()];
            if (!CellCoder.isBlank(toMove) && !owner.equals(getOwnerPos(newPos))) {
                return false;
            }

        }
        return true;
    }

    private Point getOwnerPos(Point posInBound) {
        return owners[posInBound.getX()][posInBound.getY()];
    }

    public Map<Point, Set<Point>> getMoveOrder() {

        Map<Point, Set<Point>> re = new HashMap<>();
        for (Point point : getBlankPoint()) {
            Point up = point.up();

            Point left = point.left();
            Point down = point.down();
            Point right = point.right();
            int upCell = this.getCellByPos(up);
            int leftCell = this.getCellByPos(left);
            int downCell = this.getCellByPos(down);
            int rightCell = this.getCellByPos(right);
            if (upCell != 0 && !CellCoder.isBlank(upCell)) {
                moveByDir(up, Point.of(-1, 0), re.computeIfAbsent(getOwnerPos(up), k -> new HashSet<>(4)));
            }
            if (leftCell != 0 && !CellCoder.isBlank(leftCell)) {
                moveByDir(left, Point.of(0, 1), re.computeIfAbsent(getOwnerPos(left), k -> new HashSet<>(4)));
            }
            if (downCell != 0 && !CellCoder.isBlank(downCell)) {
                moveByDir(down, Point.of(1, 0), re.computeIfAbsent(getOwnerPos(down), k -> new HashSet<>(4)));
            }
            if (rightCell != 0 && !CellCoder.isBlank(rightCell)) {
                moveByDir(right, Point.of(0, -1), re.computeIfAbsent(getOwnerPos(right), k -> new HashSet<>(4)));
            }
        }
        for (Map.Entry<Point, Set<Point>> entry : re.entrySet()) {
            entry.getValue().remove(entry.getKey());
        }
        return re;
    }

    public Set<String> getNextStep() {
        HashSet<String> re = new HashSet<>();
        Map<Point, Set<Point>> moveOrder = getMoveOrder();
        for (Map.Entry<Point, Set<Point>> entry : moveOrder.entrySet()) {
            for (Point point : entry.getValue()) {
                HRDImpl move = move(entry.getKey(), point);
                re.add(move.identity());
            }
        }
        re.remove(this.id);
        return re;
    }

    public Node findTarget() {
//        HashMap<String, Set<String>> repo = new HashMap<>();
        long startTime = System.currentTimeMillis();

        Deque<Node> toFind = new LinkedList<>();
        HashSet<String> added = new HashSet<>();
        toFind.add(new Node(id));
        added.add(id);
        int i = 0;
        while (i <= 1000000 && !toFind.isEmpty()) {
            i++;
            Node node = toFind.removeFirst();
            String s = node.data;
            if (isTarget(s)) {
                System.out.println("找到答案: " + s + "，总共步数：" + node.size());
                System.out.println("耗时ms： " + (System.currentTimeMillis() - startTime));
                return node;
            }
            HRDImpl hrd = HRDImpl.fromIdentity(s);
            Set<String> next = hrd.getNextStep();

//            repo.put(s, next);
            for (String n : next) {
                if (added.contains(n)) {
                    continue;
                }
                toFind.add(new Node(n, node));
                added.add(n);
            }
        }
        return null;
    }

    private void moveByDir(Point anyPosInBound, Point delta, Set<Point> next) {
        if (next.contains(delta)) {
            return;
        }
        if (canMove(anyPosInBound, delta)) {
            next.add(delta);
            moveByDir(anyPosInBound, delta.up(), next);
            moveByDir(anyPosInBound, delta.down(), next);
            moveByDir(anyPosInBound, delta.left(), next);
            moveByDir(anyPosInBound, delta.right(), next);
        }
    }


    public String identity() {
        return id;

    }
}
